package edu.ssdut.revise.api;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.github.javaparser.utils.Pair;
import edu.ssdut.revise.core.match.CUMatcher;
import edu.ssdut.revise.core.match.MatchStore;
import edu.ssdut.revise.core.match.TwoFileMatcher;
import edu.ssdut.revise.core.refactoringDetection.TwoFileRefactoringDetector;
import edu.ssdut.revise.exceptions.*;
import edu.ssdut.revise.models.CUWithInfo;
import edu.ssdut.revise.utils.javaParserUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static edu.ssdut.revise.utils.jGitUtils.gitInput;

public class RefactoringVisCLI {
    @Parameter(names = {"--help", "-h"}, description = "Display usage", order = 0)
    private Boolean help = false;
    @Parameter(names = {"--base-dir", "-b"}, description = "The directory path of the base version")
    private String baseDir;
    @Parameter(names = {"--modified-dir", "-m"}, description = "The directory path of the modified version")
    private String modifiedDir;
    @Parameter(names = {"--local-git", "-l"},
            description = "Input with local git repository, input in the order of the path of the local git repository, start commit hash and end hash. Split with comma. For example:'-l /home/foril/projects/revise/.git,2fd61c,3625b'")
    private List<String> paramsForLocal = new ArrayList<>();
    @Parameter(names = {"--remote-git", "-r"},
            description = "Input with remote git repository, input in the order of the url of the remote git repository, start commit hash and end hash. Split with comma. For example:'-r https://gitee.com/foril/refactoring-vis.git,2fd61c,3625b'")
    private List<String> paramsForRemote = new ArrayList<>();

    public static void main(String[] args) {
        try {
            RefactoringVisCLI cli = new RefactoringVisCLI();
            JCommander commander = JCommander.newBuilder()
                    .addObject(cli)
                    .build();
            commander.setProgramName("RefactoringVis");
            try {
                commander.parse(args);
            } catch (ParameterException e) {
                System.err.println("=".repeat(20));
                System.err.println("illegal option, please check your parameters!");
                System.err.println("=".repeat(20));
                System.exit(0);
            }

            if (cli.help) {
                commander.usage();
            } else if (cli.baseDir != null && cli.modifiedDir != null) {
                // run with two directories
                cli.run(cli.baseDir, cli.modifiedDir);
            } else if (cli.paramsForLocal.size() == 3) {
                // run with local repo
                cli.gitRun(cli.paramsForLocal.get(0), cli.paramsForLocal.get(1), cli.paramsForLocal.get(2), true);
            } else if (cli.paramsForRemote.size() == 3) {
                cli.gitRun(cli.paramsForRemote.get(0), cli.paramsForRemote.get(1), cli.paramsForRemote.get(2), false);
            } else {
                System.err.println("=".repeat(20));
                System.err.println("illegal parameter, please check your parameters!");
                System.err.println("=".repeat(20));
            }
        } catch (Exception e) {
            System.err.println("input error! Please cheok your operation!");
            e.printStackTrace();
        }
    }

    /**
     * run CLI Api, print detected refactorings
     *
     * @param baseDir     <b>directory</b> of the base version
     * @param modifiedDir <b>directory</b> of modified version
     */
    public void run(String baseDir, String modifiedDir) {
        // match files
        try {
            CUMatcher cuMatcher = new CUMatcher(baseDir, modifiedDir);
            cliDisplay(cuMatcher.getCuMatchStore());
        } catch (NotExistOrNotDirectoryException e) {
            System.err.println("=".repeat(20));
            System.err.println("file should be directory path, while non-directory path or no-existing path received!");
            System.err.println("=".repeat(20));
        } catch (ParserException e) {
            System.err.println("=".repeat(20));
            System.err.println(e.getMessage());
            System.err.println("=".repeat(20));
        }
    }


    /**
     * run git CLI Api, print detected refactorings
     *
     * @param repoString    the position of the repository, local or remote
     * @param startCommitId the hash of starting commit to be detected
     * @param endCommitId   the hash of ending commit to be detected
     * @param ifLocal       if the repoString is local path
     */
    public void gitRun(String repoString, String startCommitId, String endCommitId, Boolean ifLocal) {
        MatchStore<CUWithInfo> cuMatchStore;
        try {
            if (ifLocal) {
                cuMatchStore = gitInput(repoString, startCommitId, endCommitId, true);
            } else {
                cuMatchStore = gitInput(repoString, startCommitId, endCommitId, false);
            }
        } catch (NotExistOrNotDirectoryException e) {
            System.err.println("=".repeat(20));
            System.err.println("repo path should be directory path, while non-directory path or no-existing path received!");
            System.err.println("=".repeat(20));
            return;
        } catch (GitOperationException e) {
            System.err.println("=".repeat(20));
            System.err.println("Git operation exception encountered!");
            System.err.println("=".repeat(20));
            return;
        } catch (IllegalCommitHashException e) {
            System.err.println("=".repeat(20));
            System.err.println("illegal commit hash encountered: " + e.getMessage());
            System.err.println("=".repeat(20));
            return;
        } catch (NotLegalRepositoryException e) {
            System.err.println("=".repeat(20));
            System.err.println("the repository identifier provided is illegal!");
            System.err.println("=".repeat(20));
            return;
        } catch (IOException e) {
            System.err.println("=".repeat(20));
            System.err.println("IO exception encountered!");
            System.err.println("=".repeat(20));
            return;
        } catch (ParserException e) {
            System.err.println("=".repeat(20));
            System.err.println("syntax can not be parsed: " + e.getMessage());
            System.err.println("=".repeat(20));
            return;
        }

        cliDisplay(cuMatchStore);
    }

    private void cliDisplay(MatchStore<CUWithInfo> cuMatchStore) {
        // unmatched cus
        for (CUWithInfo cuWithInfo : cuMatchStore.getUnmatchedNodes1()) {
            System.out.println(consoleColor("red", "-----source file deleted" + "-".repeat(28)));
            //display cu info
            System.out.println("source file deleted: " + cuWithInfo.getPathString());
            System.out.println("including:");
            cuWithInfo.getCu().getTypes().forEach(td -> System.out.println(javaParserUtils.typeDeclaration2Signature(td)));
        }
        for (CUWithInfo cuWithInfo : cuMatchStore.getUnmatchedNodes2()) {
            System.out.println(consoleColor("green", "-----source file added" + "-".repeat(30)));

            System.out.println("source file added: " + cuWithInfo.getPathString());
            System.out.println("including:");
            cuWithInfo.getCu().getTypes().forEach(td -> System.out.println(javaParserUtils.typeDeclaration2Signature(td)));
        }
        // for each pair of files
        for (Pair<CUWithInfo, CUWithInfo> cuPair : cuMatchStore.getMatched()) {
            System.out.println(consoleColor("yellow", "-----source file modified" + "-".repeat(27)));
            //display pair info
            CUWithInfo a = cuPair.a;

            if (a.getPathString().endsWith("TypeDeclMatcher.java")) {
                System.out.println("aaaa");
            }

            if (a.getRepoIdentifier() != null) {
                System.out.println("in Repository: " + a.getRepoIdentifier());
                System.out.println("in Commit: " + a.getCommentId());
                System.out.println("file: " + a.getPathString());
            } else {
                System.out.println(a.getPathString());
            }
            System.out.println("MATCHED WITH");
            CUWithInfo b = cuPair.b;
            if (b.getRepoIdentifier() != null) {
                System.out.println("in Repository: " + b.getRepoIdentifier());
                System.out.println("in Commit: " + b.getCommentId());
                System.out.println("file: " + b.getPathString());
            } else {
                System.out.println(b.getPathString());
            }

            // display refactorings
            TwoFileMatcher matcher = new TwoFileMatcher(cuPair.a, cuPair.b);
            // refactorings detect
            TwoFileRefactoringDetector detector = new TwoFileRefactoringDetector(matcher);
            detector.detect();
            // display
            System.out.println("  >>>>>>>>>>>>>>>>>>>> type declaration refactorings:");
            detector.getTypeDeclarationRefactorings().forEach(System.out::println);
            System.out.println("  >>>>>>>>>>>>>>>>>>>> method refactorings:");
            detector.getMethodRefactorings().forEach(System.out::println);
            System.out.println("  >>>>>>>>>>>>>>>>>>>> constructor refactorings:");
            detector.getConstructorRefactorings().forEach(System.out::println);
            System.out.println("  >>>>>>>>>>>>>>>>>>>> variable refactorings:");
            detector.getVariableRefactorings().forEach(System.out::println);
        }
    }

    String consoleColor(String color, String text) {
        Map<String, String> map = new HashMap<>();
        map.put("red", "31");
        map.put("green", "32");
        map.put("yellow", "33");
        if (map.get(color) != null) {
            return "\033[1;" + map.get(color) + "m" + text + "\033[0m";
        }
        return "";
    }

}
